static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
    int headers_len, data_len, i, fd_stdin[2], fd_stdout[2];
    const char *status;
    char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p;
    struct mg_request_info ri;
    struct cgi_env_block blk;
    FILE *in, *out;
    pid_t pid;

    prepare_cgi_environment(conn, prog, &blk);

    // CGI must be executed in its own directory. 'dir' must point to the
    // directory containing executable program, 'p' must point to the
    // executable program name relative to 'dir'.
    (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog);
    if ((p = strrchr(dir, DIRSEP)) != NULL) {
        *p++ = '\0';
    } else {
        dir[0] = '.', dir[1] = '\0';
        p = (char *) prog;
    }

    pid = (pid_t) -1;
    fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;
    in = out = NULL;

    if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {
        send_http_error(conn, 500, http_500_error,
                "Cannot create CGI pipe: %s", strerror(ERRNO));
        goto done;
    } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
                    fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {
        goto done;
    } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL ||
            (out = fdopen(fd_stdout[0], "rb")) == NULL) {
        send_http_error(conn, 500, http_500_error,
                "fopen: %s", strerror(ERRNO));
        goto done;
    }

    setbuf(in, NULL);
    setbuf(out, NULL);

    // spawn_process() must close those!
    // If we don't mark them as closed, close() attempt before
    // return from this function throws an exception on Windows.
    // Windows does not like when closed descriptor is closed again.
    fd_stdin[0] = fd_stdout[1] = -1;

    // Send POST data to the CGI process if needed
    if (!strcmp(conn->request_info.request_method, "POST") &&
            !forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
        goto done;
    }

    // Now read CGI reply into a buffer. We need to set correct
    // status code, thus we need to see all HTTP headers first.
    // Do not send anything back to client, until we buffer in all
    // HTTP headers.
    data_len = 0;
    headers_len = read_request(out, INVALID_SOCKET, NULL,
            buf, sizeof(buf), &data_len);
    if (headers_len <= 0) {
        send_http_error(conn, 500, http_500_error,
                "CGI program sent malformed HTTP headers: [%.*s]",
                data_len, buf);
        goto done;
    }
    pbuf = buf;
    buf[headers_len - 1] = '\0';
    parse_http_headers(&pbuf, &ri);

    // Make up and send the status line
    status = get_header(&ri, "Status");
    conn->request_info.status_code = status == NULL ? 200 : atoi(status);
    (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n", conn->request_info.status_code);

    // Send headers
    for (i = 0; i < ri.num_headers; i++) {
        mg_printf(conn, "%s: %s\r\n",
                ri.http_headers[i].name, ri.http_headers[i].value);
    }
    (void) mg_write(conn, "\r\n", 2);

    // Send chunk of data that may be read after the headers
    conn->num_bytes_sent += mg_write(conn, buf + headers_len,
            (size_t)(data_len - headers_len));

    // Read the rest of CGI output and send to the client
    send_file_data(conn, out, INT64_MAX);

done:
    if (pid != (pid_t) -1) {
        kill(pid, SIGKILL);
    }
    if (fd_stdin[0] != -1) {
        (void) close(fd_stdin[0]);
    }
    if (fd_stdout[1] != -1) {
        (void) close(fd_stdout[1]);
    }

    if (in != NULL) {
        (void) fclose(in);
    } else if (fd_stdin[1] != -1) {
        (void) close(fd_stdin[1]);
    }

    if (out != NULL) {
        (void) fclose(out);
    } else if (fd_stdout[0] != -1) {
        (void) close(fd_stdout[0]);
    }
}

static pid_t spawn_process(struct mg_connection *conn, const char *prog,
        char *envblk, char *envp[], int fd_stdin,
        int fd_stdout, const char *dir) {
    pid_t pid;
    const char *interp;

    envblk = NULL; // Unused

    if ((pid = fork()) == -1) {
        // Parent
        send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
    } else if (pid == 0) {
        // Child
        if (chdir(dir) != 0) {
            cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
        } else if (dup2(fd_stdin, 0) == -1) {
            cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO));
        } else if (dup2(fd_stdout, 1) == -1) {
            cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO));
        } else {
            (void) dup2(fd_stdout, 2);
            (void) close(fd_stdin);
            (void) close(fd_stdout);

            // Execute CGI program. No need to lock: new process
            interp = conn->ctx->config[CGI_INTERPRETER];
            if (interp == NULL) {
                (void) execle(prog, prog, NULL, envp);
                cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
            } else {
                (void) execle(interp, interp, prog, NULL, envp);
                cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
                        strerror(ERRNO));
            }
        }
        exit(EXIT_FAILURE);
    } else {
        // Parent. Close stdio descriptors
        (void) close(fd_stdin);
        (void) close(fd_stdout);
    }

    return pid;
}
